﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Drawing.Design;
using System.Net;
using System.IO;

namespace WebPALTT.Lib
{
    /// <summary>
    /// A Web Request action.
    /// </summary>
    [Serializable]
    public class ActionItem_WebRequest : ActionItem
    {
        /// <summary>
        /// Static method containing the CustomType of this action for comparison.
        /// </summary>
        public static string Type { get { return "Web Request"; } }
        [CategoryAttribute("Action Properties"), ReadOnlyAttribute(true)]
        public override ActionType ActionType { get { return ActionType.Action; } }
        [CategoryAttribute("Action Properties"), ReadOnlyAttribute(true)]
        public override string CustomType { get { return ActionItem_WebRequest.Type; } }
        [CategoryAttribute("Action Properties"), ReadOnlyAttribute(true)]
        public override System.Drawing.Image Icon { get { return global::WebPALTT.Lib.Icons.world_add; } }
        public override bool HasChildren { get { return false; } }
        public override bool CanAddChildren { get { return false; } }
        public override string[] DataHeaders { get { return new string[] { "Response", "Response Status Code", "Response Status Description", "Response Length", "Cookie Count" }; } }
        public override string[] DataHeaders_Save { get { return new string[] { "Response Status Code", "Response Length" }; } }

        public override ExecutionStatus ExecutionStatus { get { return base.mExecutionStatus; } }

        String mMethod;
        Uri mUri;
        String mHttpVersion;
        List<KVP_Type<String, String>> mHeaders;
        String mContent;
        bool mSaveResponse;
        int mTimeOut;

        #region Properties
        /// <summary>
        /// Gets or set the Http method (e.g. GET, POST, ...).
        /// </summary>
        [DefaultValue("GET")]
        public String Method
        {
            get { return mMethod; }
            set { mMethod = value; }
        }

        /// <summary>
        /// Gets or set the Uri of the request.
        /// </summary>
        public Uri Uri
        {
            get { return mUri; }
            set { mUri = value; }
        }

        /// <summary>
        /// Gets or set the http protocol version (Only 1.0 and 1.1 are supported).
        /// </summary>
        [DefaultValue("1.1")]
        public String HttpVersion
        {
            get { return mHttpVersion; }
            set { mHttpVersion = value; }
        }
        /// <summary>
        /// Gets or Sets the Timeout value of the request in seconds.
        /// </summary>
        [Description("The Timeout value of the request in seconds."), DefaultValue(100)]
        public int Timeout { get { return mTimeOut; } set { mTimeOut = value; } }

        /// <summary>
        /// Gets or set the array of http headers for the request as key-value pairs.
        /// </summary>
        public List<KVP_Type<String, String>> Headers
        {
            get { return mHeaders; }
            set { mHeaders = value; }
        }
        /// <summary>
        /// Returns the header value if it exists.
        /// </summary>
        /// <param name="header"></param>
        /// <returns></returns>
        public string GetHeader(string header)
        {
            if (header == null)
                return null;
            KVP_Type<String, String> head_val = mHeaders.Find(
                delegate(KVP_Type<String, String> val)
                {
                    if (val.Key.ToUpper().Trim().Equals(header.ToUpper().Trim()))
                        return true;
                    return false;
                });
            if (head_val != null)
                return head_val.Value;
            return null;
        }
        /// <summary>
        /// Changes the header value if it exits, otherwise adds it.
        /// </summary>
        /// <param name="header"></param>
        /// <param name="value"></param>
        public void SetHeader(string header, string value)
        {
            if (header == null)
                return;
            KVP_Type<String, String> head_val = mHeaders.Find(
                delegate(KVP_Type<String, String> val)
                {
                    if (val.Key.ToUpper().Trim().Equals(header.ToUpper().Trim()))
                        return true;
                    return false;
                });
            if (head_val != null)
                head_val.Value = value ?? "";
            else
                mHeaders.Add(new KVP_Type<string,string>(header, (value ?? "")));
        }
        /// <summary>
        /// Gets or set a value to indicate if the response whould be saved during test run.
        /// </summary>
        [Description("If set to TRUE the response content will be saved to memory for use in script actions and will also be saved to the results." + 
            "NB: A large number of results being saved can have an impact on performance."), DefaultValue(true)]
        public bool SaveResponse { get { return mSaveResponse; } set { mSaveResponse = value; } }

        /// <summary>
        /// Gets or set the body of the http request (only available for POST requests). 
        /// </summary>        
        [Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public String Content
        {
            get { return mContent; }
            set { mContent = value; }
        }
        #endregion
        #region Constructors & Serialization
        public ActionItem_WebRequest() : this("") { }

        public ActionItem_WebRequest(String name) : this(name, "GET", null, "1.0", new KVP_Type<String, String>[] { }, "") { }
        /// <summary>
        /// Creates a new web request action item.
        /// </summary>
        /// <param name="method">The method for the request</param>
        /// <param name="uri">The Uri of the request</param>
        /// <param name="httpVersion">The http protocol version</param>
        /// <param name="headers">List of headers for the request</param>
        /// <param name="content">The body of the request</param>
        public ActionItem_WebRequest(String name, String method, Uri uri, String httpVersion, KVP_Type<String, String>[] headers, String content)
            : base(name)
        {
            mMethod = method;
            mUri = uri;
            mHttpVersion = httpVersion;
            mHeaders = new List<KVP_Type<string, string>> { };
            mHeaders.AddRange(headers);
            mContent = content;
            mSaveResponse = true;
            mTimeOut = 100;
        }
        
        public ActionItem_WebRequest(SerializationInfo info, StreamingContext ctxt)
            : base(info, ctxt)
        {
            mMethod = (string)info.GetValue("Method", typeof(string));
            mUri = (Uri)info.GetValue("Uri", typeof(Uri));
            mHttpVersion = (string)info.GetValue("HttpVersion", typeof(string));            
            mContent = (string)info.GetValue("Content", typeof(string));

            mHeaders = new List<KVP_Type<string, string>> { };
            foreach(KeyValuePair<String, String> pair in (KeyValuePair<String, String>[])info.GetValue("Headers", typeof(KeyValuePair<String, String>[])))
                mHeaders.Add(new KVP_Type<string, string>(pair));
            
            try { mSaveResponse = info.GetBoolean("SaveResponse"); }
            catch (SerializationException) { mSaveResponse = true; }
            try { mTimeOut = info.GetInt32("TimeOut"); }
            catch (SerializationException) { mTimeOut = 100; ; }
        }

        public override void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            base.GetObjectData(info, ctxt);
            info.AddValue("Method", mMethod);
            info.AddValue("Uri", mUri);
            info.AddValue("HttpVersion", mHttpVersion);
            KeyValuePair<string, string>[] headers = new KeyValuePair<string, string>[mHeaders.Count];
            for (int i = 0; i < mHeaders.Count; i++)
                headers[i] = mHeaders[i].KeyValuePair;
            info.AddValue("Headers", headers);
            info.AddValue("Content", mContent);
            info.AddValue("SaveResponse", mSaveResponse);
            info.AddValue("TimeOut", mTimeOut);
        }
        #endregion

        public override ActionItem FindChild(Guid key) { return null; }
        public override ActionItem[] FindChildren(Guid key) { return null; }
        
        public override ActionItemCollection Children { get { return null; } }
        
        public override string[] Validate()
        {
            List<string> errors = new List<string> { };
            if (Name.Equals("Then") || Name.Equals("Else") || Name.Equals("Actions"))
                errors.Add("Error: action[" + Name + "(" + ActionType + ")] is using a reserverd name.");

            return errors.ToArray();
        }

        public override Dictionary<string, object> Execute(Scenario scenario, ScenarioResults resultScene, Type scriptType, object scriptObj)
        {            
            Dictionary<string, object> results = new Dictionary<string, object>();
            ActionItem_HttpRequest httpAction = new ActionItem_HttpRequest(this);
            base.mExecutionStatus = ExecutionStatus.Complete;

            httpAction.Request.CookieContainer = new CookieContainer();
            foreach (Cookie c in scenario.Cookies)
                httpAction.Request.CookieContainer.Add(c);

            if (!String.IsNullOrEmpty(httpAction.Content))
            {
                byte[] data = Encoding.UTF8.GetBytes(httpAction.Content);
                Stream stream = httpAction.Request.GetRequestStream();
                stream.Write(data, 0, data.Length);
                stream.Close();
            }
            HttpWebResponse response = (HttpWebResponse)httpAction.Request.GetResponse();
            List<string> cookieinfo = new List<string> { };
            foreach (Cookie cookie in response.Cookies)
            {
                scenario.SetCookie(cookie);
                cookieinfo.Add(cookie.Domain ?? "" + " / " + cookie.Name + " = " + cookie.Value);
            }

            if (mSaveResponse)
            {
                if (response.Headers["Content-Encoding"] != null && response.Headers["Content-Encoding"].Equals("gzip"))
                    results.Add("Response", GZipCompression.Decompress(response.GetResponseStream()));
                else
                    results.Add("Response", new StreamReader(response.GetResponseStream()).ReadToEnd());
            }else
                results.Add("Response", "[Response Not Saved]");
            results.Add("Response Status Code", response.StatusCode.ToString());
            results.Add("Response Status Description", response.StatusDescription);
            results.Add("Response Length", response.ContentLength);
            results.Add("Cookie Count", cookieinfo.Count);
            return results;            
        }
    }

    class ActionItem_HttpRequest
    {
        private HttpWebRequest mRequest;
        private String mContent;

        #region Properties
        /// <summary>
        /// Gets or set the request object used.
        /// </summary>
        public HttpWebRequest Request
        {
            get { return mRequest; }
            set { mRequest = value; }
        }

        /// <summary>
        /// Gets or set the body of the http request (only available for POST requests). 
        /// </summary>
        public String Content
        {
            get { return mContent; }
            set { mContent = value; }
        }
        #endregion

        /// <summary>
        /// Creates a new http web request action item based on an existing web definition.
        /// </summary>
        /// <param name="rawRequest"></param>
        public ActionItem_HttpRequest(ActionItem_WebRequest rawRequest)
        {
            mContent = rawRequest.Content;

            mRequest = (HttpWebRequest)WebRequest.Create(rawRequest.Uri);
            mRequest.Method = rawRequest.Method;
            mRequest.ProtocolVersion = rawRequest.HttpVersion.Equals("1.1") ? HttpVersion.Version11 : HttpVersion.Version10;
            mRequest.AllowAutoRedirect = false;
            mRequest.ContentLength = mContent == null ? 0 : mContent.Length;
            mRequest.Timeout = rawRequest.Timeout * 1000;
            foreach (KVP_Type<String, String> head in rawRequest.Headers)
            {
                if (head.Key.ToLower().Equals("accept"))
                    mRequest.Accept = head.Value;
                else if (head.Key.ToLower().Equals("connection"))
                    if (head.Value.ToLower().Equals("keep-alive"))
                        mRequest.KeepAlive = true;
                    else if (head.Value.ToLower().Equals("close"))
                        mRequest.KeepAlive = false;
                    else
                        mRequest.Connection = head.Value;
                else if (head.Key.ToLower().Equals("content-length"))
                    continue;
                else if (head.Key.ToLower().Equals("content-type"))
                    mRequest.ContentType = head.Value;
                else if (head.Key.ToLower().Equals("expect")) { }
                else if (head.Key.ToLower().Equals("date")) { }
                else if (head.Key.ToLower().Equals("host"))
                    mRequest.Host = head.Value;
                else if (head.Key.ToLower().Equals("if-modified-since")) { }
                else if (head.Key.ToLower().Equals("range")) { }
                else if (head.Key.ToLower().Equals("referer"))
                    mRequest.Referer = head.Value;
                else if (head.Key.ToLower().Equals("user-agent"))
                    mRequest.UserAgent = head.Value;
                else
                    mRequest.Headers.Add(head.Key, head.Value);
            }
        }
    }
}
